package org.andengine.shmup;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;

import org.anddev.andengine.engine.handler.timer.ITimerCallback;
import org.anddev.andengine.engine.handler.timer.TimerHandler;
import org.anddev.andengine.entity.sprite.AnimatedSprite;
import org.anddev.andengine.opengl.texture.region.TiledTextureRegion;
import org.anddev.andengine.util.Debug;
import org.andengine.bulletml.simple.Action;
import org.andengine.bulletml.simple.ActionSeq;
import org.andengine.bulletml.simple.BossData;
import org.andengine.bulletml.simple.PointXML;

import team2.game.crow.Laser;

public class Boss extends Enemy {
	protected BossData data;
	protected ArrayList<AnimatedSprite> lasers = new ArrayList<AnimatedSprite>();
	protected ArrayList<AnimatedSprite> addComps = new ArrayList<AnimatedSprite>();
	protected HashMap<String, TimerHandler> timers = new HashMap<String, TimerHandler>();
	//protected HashMap<String, TimerHandler> sTimers = new HashMap<String, TimerHandler>();
	protected HashMap<AnimatedSprite, Float> coreDestY = new HashMap<AnimatedSprite, Float>();
	protected int chargeCounter = 0, k = 0,
			actReps = 0, powerLevel= 0, chargeTiles = 0, ptsVisited = 0;
	protected double regen = 0;
	protected float dmgMod = 1.0f;
	protected boolean simultAct = true, fullCharge = false;
	private static final String NONE = "none";
	protected String actingSeq = NONE;
	protected static final float ENTRANCE_TIME = 4.8f;
	protected PointXML target;
	public boolean isInvincible = false, isEntering = true;

	public Boss(float pX, float pY, TiledTextureRegion pTextureRegion,
			final BossData bd, TiledTextureRegion beam) {
		super(pX, pY, pTextureRegion);
		this.data = bd;
		this.hp = data.getHealth();
		
		simultAct = this.data.getActMode().equalsIgnoreCase("ALTERNATING") ;
		
		this.v.x = bd.getvX();
		this.v.y = bd.getvY();

		if(data.hasSequences()){
			for(ActionSeq as: this.data.getSequences()){
				this.timerInit(as.getActions().values(), beam);
			}
		}
		this.timerInit(this.data.getActions(), beam);
		
		this.isGrunt = false;
	}

	public void act(float delta) {
		if(this.data.hasSequences()){
			for (ActionSeq as : this.data.getSequences()) {
				if(this.actingSeq.equals(NONE) || this.actingSeq.equals(as.getLabel())){
					as.timer += delta;
					this.seqHelp(as, delta);
				}
			}
		}
		for (Action a : this.data.getActions()) {
			if(this.actingSeq.equals(NONE)){
				a.timer += delta;
				this.actHelp(a);//, NONE);
				if(a.getLabel().contains("move")) this.move(delta, a);
			}
		}
	}

	protected int seqHelp(ActionSeq as, float delta) {
		int cVal = 0;
		if(as.active == false){
			if (as.timer >= as.getRecovery()-(.1*(float)(this.powerLevel/3f))) {
				this.actReps = as.getRepeat() + (int)(this.powerLevel/2);
				as.compileQ();
				as.nextActions();
				as.active = true;
				this.actingSeq = as.getLabel();
			}
			else return 0;
		}
		
		for (Action a : as.getCurrentActions()) {
			a.timer += delta;
			cVal = this.actHelp(a);//, as.getLabel());
			//Debug.d("Action: " + a.getLabel() + " Timer: " + a.timer + "s");
			if(a.getLabel().contains("move")) this.move(delta, a);
		}
		
		if (cVal == -1) {
			if (as.hasNextSet() == false) {
				this.actReps--;

				if (actReps < 0) {
					as.active = false;
					as.timer = 0;
					this.actingSeq = NONE;
					return -1;
				}
				else{
					as.compileQ();
				}			
			}
			else as.nextActions();
		}
		return 1;
	}

	protected int actHelp(Action a){ //, final String source) {
		
		if (a.timer >= a.getRecovery()-(.1*(float)(this.powerLevel/3)) && a.active == false &&  this.checkReqs(a)) {
			this.toggle(a, true);//, source, source.equals("none"));
			return 1;
		}
		else if ((a.timer >= a.getFiringInt()*(a.getRepeat()+1+(int)(this.powerLevel/3))) && a.active == true) {
			this.toggle(a, false);//, source, source.equals("none"));
			return -1;
		}
		if(a.getLabel().equals("chargeBeam")){
			if(!a.active && a.timer > (this.chargeCounter+1)*(a.getRecovery()/this.chargeTiles)){
				for(AnimatedSprite addon : this.addComps){
					if(addon.getUserData().equals("core3"))
						addon.stopAnimation((++chargeCounter)%chargeTiles);
				}
			}			
		}
		return 0;
	}

	private boolean checkReqs(final Action a) {
		if(a.getReqs().length == 0) return true;
		for(String s: a.getReqs()){
			if(s.contains("LEVEL")){
				if(s.compareTo("OVERLEVEL" + this.powerLevel) >= 0 && s.startsWith("OVER")){
					return false;
				} else if(s.compareTo("UNDERLEVEL" + this.powerLevel) <= 0 && s.startsWith("UNDER")){
					return false;
				} else if(s.compareTo("LEVEL" + this.powerLevel) != 0  && s.startsWith("LEVEL")){
					return false;
				}
				
			}
			else if(s.contains("HP%")){
				if(s.compareTo("OVERHP%" + Math.round(100*this.hp/this.data.getHealth())) >= 0 && s.startsWith("OVER")){
					return false;
				}
				if(s.compareTo("UNDERHP%" + Math.round(100*this.hp/this.data.getHealth())) <= 0 && s.startsWith("UNDER")){
					return false;
				}
			}
		}
		return true;
	}

	protected void toggle(Action a, final boolean set) {
		
		if(fullCharge && set){ 
			a.timer = 0;
			return;
		}
		
		a.active = set;
		a.timer = 0;
		
		if (a.getLabel().contains("Beam")) {
			
			for (AnimatedSprite l : this.lasers) {
				l.setVisible(set);
				if (set == true)
					l.animate(15, true);
			}
			if (simultAct == false) {
				this.fullCharge = set;
				if (this.fullCharge == false) {
					this.chargeCounter = 0;
				} else {
					for(Action at: this.data.getActions()){
						if(at.active && timers.containsKey(at.getLabel())){
							at.active = false;
							at.timer = 0;
							this.unregisterUpdateHandler(timers.get(at.getLabel()));
						}
					}
				}
			}
			
		} else if(a.getLabel().contains("wait")){
			return;
		} else if (a.getFiringGap() > 0) {
			if (set)
				this.registerUpdateHandler(this.timers.get(a.getLabel()));
			else
				this.unregisterUpdateHandler(this.timers.get(a.getLabel()));
		}
		
		this.augmentHandler(a.getAugment(), set);
		/*
		this.seqActive = (a.active && partOfSeq);
		if(this.seqActive){				
			this.actingSeq = seqName;
		} else {				
			this.actingSeq = "none";
		}*/
	}

	public void augmentHandler(String buff, boolean set){
		if (buff.contains("regen")) {
			if (set == false) {
				this.regen = 0;
			} else if (buff.contains("S")) {
				this.regen = 5;
			} else if (buff.contains("L")) {
				this.regen = 20;
			}

		} else if (buff.contains("invuln")) {
			this.isInvincible = set;
		} else if (buff.contains("dmg")){
			this.dmgMod = data.getDefMod();
			
			if(buff.contains("Up") && set)
				dmgMod = data.getDefMod() + Float.parseFloat(buff.substring(buff.indexOf("dmgUp")))/100;
			else if(buff.contains("Down") && set)
				dmgMod = data.getDefMod() - Float.parseFloat(buff.substring(buff.indexOf("dmgDown")))/100;
		}
	}
	
	public void move(float delta, final Action a) {

		if(a.getLabel().contains("moveToTargets")){
			float t = a.getFiringInt()/a.getPoints().size();
			int i = 0;
			
			for(PointXML p : a.getPoints()){
				target = p; 
				i++;
				if(i > ptsVisited) break;
			}
			this.v.x = (this.getX() - target.x)/(t);
			this.v.y = (this.getY() - target.y)/(t);
			
			if(this.getX()+this.v.x == target.x && this.getY()+this.v.y == target.y){
				ptsVisited++;
			}
			
		} else if (a.getLabel().contains("moveHStrafe")) {
			if (this.getX() >= CAMERA_WIDTH)
				this.v.x = -Math.abs(this.data.getvX());
			else if (this.getX() <= 0)
				this.v.x = Math.abs(this.data.getvX());
		} else if (a.getLabel().contains("moveCircle")) {
			this.v.x = a.getRotDir()*(a.getRadialOffset()/a.getFiringInt()) * (float) -Math.sin(Math.toRadians(360 + a.getFanAngleAdd())*a.timer/a.getFiringInt());
			this.v.y = a.getRotDir()*(a.getRadialOffset()/a.getFiringInt()) * (float) Math.cos(Math.toRadians(360 + a.getFanAngleAdd())*a.timer/a.getFiringInt());
		} 
		
		this.setPosition(this.getX() + (this.v.x*delta),
							this.getY() + (this.v.y*delta));
		
		if (!addComps.isEmpty()) {
			for (AnimatedSprite as : this.addComps) {
				as.setPosition(as.getX() + this.v.x * delta, as.getY()
						+ this.v.y * delta);
			}
		}
		if (!lasers.isEmpty()){
			for (AnimatedSprite l : this.lasers) {
				l.setPosition(l.getX() + this.v.x * delta, l.getY()
						+ this.v.y * delta);
			}
		}
		// Debug.d("canMove: " + canMove);
		//Debug.d("Velocity: <" + this.v.x + ", " + this.v.y + ">");
		//Debug.d("Position: (" + this.getX() + ", " + this.getY() + ")");
	}

	/*
	 * USed for the scrolling in part.
	 */
	protected void moveE(float delta) {
		if (this.timeAlive < ENTRANCE_TIME) {
			this.v.y = (this.getHeight()) / ENTRANCE_TIME;
			super.move(delta);
			for (AnimatedSprite c : this.addComps) {
				c.setPosition(
						c.getX(),
						c.getY() + delta
								* (this.coreDestY.get(c) - c.getInitialY())
								/ ENTRANCE_TIME);
			}
		} else {
			this.v.y = 0;
			this.isEntering = false;
			this.isInvincible = false;
			this.init();
		}
	}

	protected void fireBullets(final List<PointXML> lp, Action a) {

		float fan = 0;
		int counter = 0, distMod = 0;
		if (a.getLabel().compareTo("altFan") == 0) {
			counter = 0;
			while (counter < lp.get(k).multis) {

				distMod = (counter++) + ((lp.get(k).multis + 1) % 2);
				
				fireBullet(lp.get(k),
						(float) Math.toRadians((lp.get(k).angle + Math.pow(-1,
								distMod)
								* (a.getFanAngle() / lp.get(k).multis)
								* (int) ((distMod + 1) / 2))), a.getRadialOffset(),
						false);
			}
			k = (k + 1) % a.getPoints().size();
		} else {
			for (PointXML p : lp) {
				counter = 0;
				if(a.getLabel().contains("oscFan")){
					fireBullet(p, (float) Math.toRadians(p.angle + calcOscil(a)), a.getRadialOffset());
				}
				else if (a.getLabel().contains("Fan")) {
					while (counter <= p.multis+this.powerLevel) {
						distMod = counter + ((p.multis+this.powerLevel+1)% 2);

						if (!a.getFanSpread().equals("CONSTANT")
								&& !(counter == 0 && p.multis % 2 == 1)) {
							fan = ((int) (counter + (p.multis) - (4 + (p.multis) % 2)) / 2)
									* angleDist(a.getFanAngle(), p.multis);
							// Debug.d(fan + " degrees");
						} else {
							fan = (a.getFanAngle() / p.multis)
									* (int) ((distMod + 1) / 2);
						}
						
						if(counter != 0){
							fan += (float)Math.sin(Math.PI *(a.timer/a.getFiringInt())*a.getSpcFanRep()) * a.getFanAngleAdd();
						}
						
						fireBullet(
								p,
								(float) Math.toRadians((p.angle
										+ Math.pow(-1, distMod) * (fan) + 360)),
								a.getRadialOffset());
						counter++;
					}
				} else if(a.getLabel().contains("fireSpiral")) {
						fireBullet(p, a.getRotDir()*(float) (Math.toRadians(p.angle) + (2*Math.PI*(a.timer/a.getFiringGap())/p.multis)), a.getRadialOffset());
				} else if(a.getLabel().contains("radSpiral")) {
					while(counter<=p.multis){
						fireBullet(p,
								(float) Math.toRadians((p.angle + (360 / (p.multis+this.powerLevel))* counter++) 
										+ a.getFanAngleAdd()*a.timer/a.getFiringInt()), a.getRadialOffset(), false);
						counter++;
					}
				} else if (a.getLabel().contains("fireRadial")
						|| a.getLabel().contains("fireRing")) {
					while (counter <= p.multis) {
						fireBullet(p,
								(float) Math
										.toRadians((p.angle + (360 / (p.multis+this.powerLevel))
												* counter++) + a.getFanAngleAdd()*a.timer/a.getFiringInt())
												, a.getRadialOffset(), a
										.getLabel().compareTo("fireRing") == 0);
					}
				} else
					fireBullet(p, (float) Math.toRadians(p.angle));
			}
		}
	}

	protected float angleDist(float a, int i) {

		if (i > 3) {
			return angleDist(a / 3f, i - 2);
		} else if (i > 8) {
			return angleDist(a / 2f, i - 2);
		} else {
			// Debug.d("Distribute angle: " + a/2 + " degrees");
			return a / 2;
		}
	}

	protected void fireBullet(PointXML pxml, float angle) {
		fireBullet(pxml, angle, 0, false);
	}

	protected void fireBullet(PointXML pxml, float angle, float radius) {
		fireBullet(pxml, angle, radius, false);
	}

	protected void fireBullet(PointXML pxml, float angle, float radius,
			boolean isRing) {
		Laser proxy = null;
		proxy = bulletPool.obtainPoolItem();
		proxy.setDirection(-1);
		proxy.setPosition(
				this.getX() + pxml.x + radius * (float) Math.sin(angle),
				this.getY() + pxml.y + radius * (float) Math.cos(angle));

		if (isRing)
			proxy.setAngle(pxml.angle);
		else
			proxy.setAngle(angle);

		proxy.setUserData("bullet");
		this.bullets.add(proxy);
		if (!proxy.hasParent()) {
			this.getParent().attachChild(proxy);
		}
	}

	protected float calcOscil(Action a){;
		float bc = (a.timer/a.getFiringInt())*2*(a.getSpcFanRep()+1);
		return (a.getFanAngle()/2 * a.getRotDir()) + (-1*a.getFanAngle()*(float)Math.abs(Math.sin(Math.PI*bc + a.getOscStart())));
	}
	
	@Override
	protected void onManagedUpdate(float pSecondsElapsed) {
		this.isAlive = this.hp > 0;
		if (hp + regen <= data.getHealth() && regen > 0)
			this.hp += regen;
		if (this.isEntering) {
			this.timeAlive += pSecondsElapsed;
			this.moveE(pSecondsElapsed);
		} else if (this.isAlive) {
			this.timeAlive += pSecondsElapsed;
			this.act(pSecondsElapsed);
			super.onManagedUpdate(pSecondsElapsed);
		} else if (!this.isAlive) {
			for(Action a: this.data.getActions()){
				if(a.active && timers.containsKey(a.getLabel())){
					this.unregisterUpdateHandler(timers.get(a.getLabel()));
				}
			}
			this.timers.clear();
		}
	}

	/*
	 * public void setData(BossData data) { this.data = data; }
	 */
	public final BossData getData() {
		return data;
	}

	/*
	 * Initialize Lasers. Call immediately after scrolling in.
	 */
	public void init() {
		for (AnimatedSprite l : this.lasers) {
			l.setUserData("laserbeam");
			l.setRotation(180);
			l.setPosition(this.getX() + l.getX()
					- l.getTextureRegion().getTileWidth() / 2,
					this.getY() + l.getY());
			this.getParent().attachChild(l);
			l.setVisible(false);
		}
		this.isAlive = true;
		this.regen = 0;
		//this.seqActive = false;
		Debug.d(this.lasers.toString());
	}

	// Used for stuff like the second Boss core.
	public void addExternalAnimSprite(float mX, float mY,
			TiledTextureRegion ttr, long animTimes, String pUserData) {
		final AnimatedSprite addon = new AnimatedSprite(mX, mY
				- this.getHeight(), ttr);
		addon.setUserData(pUserData);
		if (ttr.getTileCount() > 1) {
			addon.animate(animTimes);
		}
		coreDestY.put(addon, mY);
		// *
		for (final Action a : this.data.getActions()) {
			if (a.getLabel().contains("charge")) {
				chargeTiles = ttr.getTileCount();
			}
		}// */
		this.addComps.add(addon);
		this.getParent().attachChild(addon);
	}
	
	public float getDmgMod(){
		return this.dmgMod;
	}
	
	protected void timerInit(Collection<Action> c, TiledTextureRegion beam){
		/* Initializes Timing components */
		for (final Action a : c) {
			Debug.d(a.getLabel());
			
			if (a.getLabel().compareTo("fireBeam") == 0
					|| a.getLabel().compareTo("chargeBeam") == 0) {
				for (PointXML px : a.getPoints()) {
					this.lasers.add(new AnimatedSprite(px.x, px.y, beam));
				}
			} else if (a.getFiringGap() > 0) {
					timers.put(a.getLabel(), new TimerHandler(a.getFiringGap(),
							true, new ITimerCallback() {
								@Override
								public void onTimePassed(
										final TimerHandler pTimerHandler) {
									fireBullets(a.getPoints(), a);
									pTimerHandler.reset();
								}
							}));
			}
		}
	}
	private float adjustAtk(HashMap<Float, Float> modMap){
		float h = (float) this.hp / (float)this.data.getHealth();
		float m = 0;
		for(float f :  modMap.keySet()){
			if(h < f)
				m =  modMap.get(f);
		}
		return m;
	}
}